1. 参考链接

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
    (基础)
  2. http://liubin.org/promises-book/ (开源 Promise 迷你书)
  3. http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/
    (进阶)
  4. https://promisesaplus.com/ (官方定义规范)

本文大部分都摘抄总结于JavaScript Promise迷你书,侵删致歉。

2. 概念

Promise是抽象异步处理对象以及对其进行各种操作的组件。

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.

A Promise is in one of these states:

pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation completed successfully.
rejected: meaning that the operation failed.

回调函数的异步处理

Node.js规定在JavaScript的回调函数的第一个参数为 Error 对象。

1
2
3
4
5
6
7
8
9
10
使用了回调函数的异步处理
----
getAsync("fileA.txt", function(error, result){
if(error){// 取得失败时的处理
throw error;
}
// 取得成功时的处理
});
----
<1> 传给回调函数的参数为(error对象, 执行结果)组合

通过async方法重写 promise 链

1
2
3
4
5
6
7
8
9
10
11
12
function getProcessedData(url) {
return downloadData(url) // returns a promise
.catch(e => {
return downloadFallbackData(url) // returns a promise
.then(v => {
return processDataInWorker(v); // returns a promise
});
})
.then(v => {
return processDataInWorker(v); // returns a promise
});
}
1
2
3
4
5
6
7
8
9
async function getProcessedData(url) {
let v;
try {
v = await downloadData(url);
} catch (e) {
v = await downloadFallbackData(url);
}
return processDataInWorker(v);
}

Promise进行异步处理

Promise把类似的异步处理对象和处理规则进行规范化, 并按照采用统一的接口来编写,而采取规定方法之外的写法都会出错。

1
2
3
4
5
6
7
8
9
10
下面是使用了Promise进行异步处理的一个例子
----
var promise = getAsyncPromise("fileA.txt");
promise.then(function(result){
// 获取文件内容成功时的处理
}).catch(function(error){
// 获取文件内容失败时的处理
});
----
<1> 返回promise对象

Constructor

1
2
3
4
var promise = new Promise(function(resolve, reject) {
// 异步处理
// 处理结束后、调用resolve 或 reject
});

Instance Method

1
promise.then(onFulfilled, onRejected)

resolve(成功)时onFulfilled 会被调用

reject(失败)时onRejected 会被调用

只想对异常进行处理时

1
promise.catch(onRejected)

Promise workflow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function asyncFunction() {

return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('Async Hello world');
}, 16);
});
}

asyncFunction().then(function (value) {
console.log(value); // => 'Async Hello world'
}).catch(function (error) {
console.log(error);
});

asyncFunction 这个函数会返回promise对象, 对于这个promise对象,我们调用它的 then 方法来设置resolve后的回调函数, catch 方法来设置发生错误时的回调函数。

promise对象会在setTimeout之后的16ms时被resolve, 这时 then 的回调函数会被调用,并输出'Async Hello world'

3. 创建Promise对象

创建XHR的promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(function onRejected(error){
console.error(error);
});

4. Promise方法

1. Promise.resolve

Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。

1
2
3
4
5
Promise.resolve(42)
----
new Promise(function(resolve){
resolve(42);
});

2. Promise.reject

Promise.reject(error)也是 new Promise() 方法的快捷方式。

1
2
3
4
5
Promise.reject(new Error("出错了"))
----
new Promise(function(resolve,reject){
reject(new Error("出错了"));
});

使用

1
2
3
Promise.reject(new Error("BOOM!")).catch(function(error){
console.error(error);
});

使用 reject 会比使用 throw 安全。
因为我们很难区分throw是我们主动抛出来的,还是因为真正的其它异常导致的。

Chrome的开发者工具提供了在程序发生异常的时候自动在调试器中break的功能。当我们开启这个功能的时候,在执行到下面代码中的 throw 时就会触发调试器的break行为。

1
2
3
var promise = new Promise(function(resolve, reject){
throw new Error("message");
});

resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,promise 状态一旦改变则不能再变。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

在then中进行reject
1
2
3
4
5
6
7
var promise = Promise.resolve();
promise.then(function () {
var retPromise = new Promise(function (resolve, reject) {
// resolve or reject 的状态决定 onFulfilled or onRejected 的哪个方法会被调用
});
return retPromise;
}).then(onFulfilled, onRejected);
1
2
3
4
5
var onRejected = console.error.bind(console);
var promise = Promise.resolve();
promise.then(function () {
return Promise.reject(new Error("this promise is rejected"));
}).catch(onRejected);

3. Thenable

类Promise对象。 拥有名为.then方法的对象。

jQuery.ajax()的返回值是一个具有 .then 方法的 jqXHR Object对象,就是thenable的:

$.ajax('/json/comment.json');// => 拥有 `.then` 方法的对象

Promise.resolve 方法将 thenable 对象转换为promise对象。

1
2
3
4
var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
console.log(value);
});

但即使一个对象具有 .then 方法,也不一定就能作为ES6 Promises对象使用。

4. Promise#then#catch

promise方法链(promise chain

then: 注册onFulfilled时的回调函数

catch: 注册onRejected时的回调函数

Promise#catch 只是 promise.then(undefined, onRejected);方法别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
promise-then-catch-flow.js
--------

function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}

var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
----------

Task A
Task B
Final Task
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
taskA异常
--------

function taskA() {
console.log("Task A");
throw new Error("throw Error @ Task A")
}
function taskB() {
console.log("Task B");// 不会被调用
}
function onRejected(error) {
console.log(error);// => "throw Error @ Task A"
}
function finalTask() {
console.log("Final Task");
}

var promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);

--------

Task A
Error: throw Error @ Task A
Final TaskPromise Anti-patterns

Promise#then不仅仅是注册一个回调函数那么简单,它还会将回调函数的返回值进行变换,创建并返回一个promise对象。

点标记法(dot notation)
要求对象的属性必须是有效的标识符(在ECMAScript 3中则不能使用保留字),

中括号标记法(bracket notation)
可以将非合法标识符作为对象的属性名使用

1
2
3
4
5
6
7
var promise = Promise.reject(new Error("message"));
promise["catch"](function (error) {
console.error(error);
});

--------
Error: message

Promise Anti-patterns

5. Promise.all

Promise.all 接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用.then方法。

传递给 Promise.all 的promise是同时开始、并行执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
var startDate = Date.now();
// 所有promise变为resolve后程序退出
Promise.all([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (values) {
console.log(Date.now() - startDate + 'ms');
// 約128ms
console.log(values); // [1,32,64,128]
});

--------
128ms(及以上)
1,32,64,128

6. Promise.race

Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理。

Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
resolve(delay);
}, delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});

--------
1

Promise.race 在第一个promise对象变为Fulfilled之后,并不会取消其他promise对象的执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var winnerPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('this is winner');
resolve('this is winner');
}, 4);
});
var loserPromise = new Promise(function (resolve) {
setTimeout(function () {
console.log('this is loser');
resolve('this is loser');
}, 1000);
});
// 第一个promise变为resolve后程序停止
Promise.race([winnerPromise, loserPromise]).then(function (value) {
console.log(value); // => 'this is winner'
});

---------
this is winner
this is winner
this is loser

5. Promise只能进行异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
var promise = new Promise(function (resolve){
console.log("inner promise"); // 1
resolve(42);
});
promise.then(function(value){
console.log(value); // 3
});
console.log("outer promise"); // 2

----
inner promise // 1
outer promise // 2
42 // 3

由于JavaScript代码会按照文件的从上到下的顺序执行,所以最开始resolve(42)被执行。这时候 promise 对象的已经变为确定状态,FulFilled被设置为了 42 。

但是即使在调用 promise.then 注册回调函数的时候promise对象已经是确定的状态,Promise也会以异步的方式调用该回调函数,这是在Promise设计上的规定方针。

绝对不能对异步回调函数(即使在数据已经就绪)进行同步调用。

如果对异步回调函数进行同步调用的话,处理顺序可能会与预期不符,可能带来意料之外的后果。

对异步回调函数进行同步调用,还可能导致栈溢出或异常处理错乱等问题。

如果想在将来某时刻调用异步回调函数的话,可以使用 setTimeout 等异步API。

Effective JavaScript — David Herman

6. 错误处理机制

promise.then(onFulfilled, onRejected)

onFulfilled 中发生异常的话,在 onRejected 中是捕获不到这个异常的。

promise.then(onFulfilled).catch(onRejected)

then 中产生的异常能在 .catch 中捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function throwError(value) {
// 抛出异常
throw new Error(value);
}
// <1> onRejected不会被调用
function badMain(onRejected) {
return Promise.resolve(42).then(throwError, onRejected);
}
// <2> 有异常发生时onRejected会被调用
function goodMain(onRejected) {
return Promise.resolve(42).then(throwError).catch(onRejected);
}
// 运行示例
badMain(function(){
console.log("BAD");
});
goodMain(function(){
console.log("GOOD");
});

---------
GOOD

goodMain 的代码则遵循了 throwError→onRejected 的调用流程。 这时候 throwError 中出现异常的话,在会被方法链中的下一个方法,即 .catch 所捕获,进行相应的错误处理。

.then 方法中的onRejected参数所指定的回调函数,实际上针对的是其promise对象或者之前的promise对象,而不是针对 .then 方法里面指定的第一个参数,即onFulfilled所指向的对象,这也是 thencatch表现不同的原因。

then&catch flow

只用then方法也可以,但意图不够明确

1
Promise.resolve(42).then(throwError).then(null, onRejected);

7. 测试

1
npm install --save-dev mocha

1. 使用done和回调函数测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//promise-done-example.js
------------------------

if (typeof Promise.prototype.done === 'undefined') {
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected).catch(function (error) {
setTimeout(function () {
throw error;
}, 0);
});
};
}
var promise = Promise.resolve();
promise.done(function () {
JSON.parse('this is not json'); // => SyntaxError: JSON.parse
});
// => 请打开浏览器的开发者工具中的控制台窗口看一下
  • done 并不返回promise对象

    • 也就是说,在done之后不能使用 catch 等方法组成方法链
  • done 中发生的异常会被直接抛给外面

    • 也就是说,不会进行Promise的错误处理(Error Handling)。done会在函数中跳过错误处理,直接抛出异常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
basic-test.js
-------------
var assert = require('power-assert');
describe('Basic Test', function () {
context('When Callback(high-order function)', function () {
it('should use `done` for test', function (done) {
setTimeout(function () {
assert(true);
done();
}, 0);
});
});
context('When promise object', function () {
it('should use `done` for test?', function (done) {
var promise = Promise.resolve(1);
// このテストコードはある欠陥があります
promise.then(function (value) {
assert(value === 1);
done();
});
});
});
});

通常情况下,assert 失败的时候,会throw一个error, 测试框架会捕获该error,来判断测试失败。

但是,Promise的情况下 .then 绑定的函数执行时发生的error 会被Promise捕获,而测试框架则对此error将会一无所知。

为了处理 assert 失败的情况,我们需要额外添加 .then(done, done); 的代码。 这就要求我们在编写Promise测试时要格外小心,忘了加上上面语句的话,很可能就会写出一个永远不会返回直到超时的测试代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
it("should use `done` for test?", function (done) {
var promise = Promise.resolve();
promise.then(function (value) {
assert(false);// => throw AssertionError
done();
});
});

--------修改
it("should use `done` for test?", function (done) {
var promise = Promise.resolve();
promise.then(function (value) {
assert(false);
}).then(done, done);
});
1
mocha basic-test.js

2. 对Promise测试

1
2
3
4
5
6
7
8
9
10
//删除了 done, 返回结果为promise对象
var assert = require('power-assert');
describe('Promise Test', function () {
it('should return a promise object', function () {
var promise = Promise.resolve(1);
return promise.then(function (value) {
assert(value === 1);
});
});
});

8. Promise的实现类库

jakearchibald/es6-promise
一个兼容 ES6 Promises 的Polyfill类库。 它基于 RSVP.js 这个兼容 Promises/A+ 的类库, 它只是 RSVP.js 的一个子集,只实现了Promises 规定的 API。

yahoo/ypromise
这是一个独立版本的 YUI 的 Promise Polyfill,具有和 ES6 Promises 的兼容性。 本书的示例代码也都是基于这个 ypromise 的 Polyfill 来在线运行的。

getify/native-promise-only
以作为ES6 Promises的polyfill为目的的类库 它严格按照ES6 Promises的规范设计,没有添加在规范中没有定义的功能。 如果运行环境有原生的Promise支持的话,则优先使用原生的Promise支持。

Promise扩展类库

kriskowal/q
类库 Q 实现了 Promises 和 Deferreds 等规范。 它自2009年开始开发,还提供了面向Node.js的文件IO API Q-IO 等, 是一个在很多场景下都能用得到的类库。

petkaantonov/bluebird
这个类库除了兼容 Promise 规范之外,还扩展了取消promise对象的运行,取得promise的运行进度,以及错误处理的扩展检测等非常丰富的功能,此外它在实现上还在性能问题下了很大的功夫。

9. callback & promise & thenable

1. Web Notification 包装函数(wrapper)

回调风格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//notification-callback.js
------------------------
function notifyMessage(message, options, callback) {
if (Notification && Notification.permission === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else if (Notification.requestPermission) {
Notification.requestPermission(function (status) {
if (Notification.permission !== status) {
Notification.permission = status;
}
if (status === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else {
callback(new Error('user denied'));
}
});
} else {
callback(new Error('doesn\'t support Notification API'));
}
}
// 运行实例
// 第二个参数是传给 `Notification` 的option对象
notifyMessage("Hi!", {}, function (error, notification) {
if(error){
return console.error(error);
}
console.log(notification);// 通知对象
});

2. Web Notification as Promise

返回promise对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//notification-as-promise.js
----------------------------

function notifyMessage(message, options, callback) {
if (Notification && Notification.permission === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else if (Notification.requestPermission) {
Notification.requestPermission(function (status) {
if (Notification.permission !== status) {
Notification.permission = status;
}
if (status === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else {
callback(new Error('user denied'));
}
});
} else {
callback(new Error('doesn\'t support Notification API'));
}
}
function notifyMessageAsPromise(message, options) {
return new Promise(function (resolve, reject) {
notifyMessage(message, options, function (error, notification) {
if (error) {
reject(error);
} else {
resolve(notification);
}
});
});
}
// 运行示例
notifyMessageAsPromise("Hi!").then(function (notification) {
console.log(notification);// 通知对象
}).catch(function(error){
console.error(error);
});

3. Web Notifications As Thenable

thenable就是一个具有 .then方法的一个对象。

then方法的参数和 new Promise(function (resolve, reject){}) 一样,在确定时执行 resolve 方法,拒绝时调用 reject 方法。

我们可以看出, Promise.resolve(thenable) 通过使用了 thenable 这个promise对象,就能利用Promise功能了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//notification-thenable.js
--------------------------

function notifyMessage(message, options, callback) {
if (Notification && Notification.permission === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else if (Notification.requestPermission) {
Notification.requestPermission(function (status) {
if (Notification.permission !== status) {
Notification.permission = status;
}
if (status === 'granted') {
var notification = new Notification(message, options);
callback(null, notification);
} else {
callback(new Error('user denied'));
}
});
} else {
callback(new Error('doesn\'t support Notification API'));
}
}
// 返回 `thenable`
function notifyMessageAsThenable(message, options) {
return {
'then': function (resolve, reject) {
notifyMessage(message, options, function (error, notification) {
if (error) {
reject(error);
} else {
resolve(notification);
}
});
}
};
}
// 运行示例
Promise.resolve(notifyMessageAsThenable("message")).then(function (notification) {
console.log(notification);// 通知对象
}).catch(function(error){
console.error(error);
});

Thenable本身并不依赖于Promise功能,但是Promise之外也没有使用Thenable的方式,所以可以认为Thenable间接依赖于Promise。

10. Deferred & Promise

deferred 介绍

The Deferred object, introduced in jQuery 1.5, is a chainable utility object created by calling the jQuery.Deferred() method. It can register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function.

Deferred最初是在Python的 Twisted 框架中被提出来的概念。 在JavaScript领域可以认为它是由 MochiKit.Asyncdojo/Deferred 等Library引入的。

jQuery.Deferred

JSDeferred

下图为jQuery.Deferred结构的简化版。当然也有的Deferred实现并没有内涵Promise。

Deferred & Promise

基于Promise实现Deferred

1
2
3
4
5
6
7
8
9
10
11
12
function Deferred() {
this.promise = new Promise(function (resolve, reject) {
this._resolve = resolve;
this._reject = reject;
}.bind(this));
}
Deferred.prototype.resolve = function (value) {
this._resolve.call(this.promise, value);
};
Deferred.prototype.reject = function (reason) {
this._reject.call(this.promise, reason);
};

实例

所谓的能对Promise状态进行操作的特权方法,指的就是能对promise对象的状态进行resolvereject等调用的方法,而通常的Promise的话只能在通过构造函数传递的方法之内对promise对象的状态进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//xhr-promise.js
-----------------

function getURL(URL) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.send();
});
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(console.error.bind(console));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//xhr-deferred.js
----------------

function Deferred() {
this.promise = new Promise(function (resolve, reject) {
this._resolve = resolve;
this._reject = reject;
}.bind(this));
}
Deferred.prototype.resolve = function (value) {
this._resolve.call(this.promise, value);
};
Deferred.prototype.reject = function (reason) {
this._reject.call(this.promise, reason);
};
function getURL(URL) {
var deferred = new Deferred();
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
deferred.resolve(req.responseText);
} else {
deferred.reject(new Error(req.statusText));
}
};
req.onerror = function () {
deferred.reject(new Error(req.statusText));
};
req.send();
return deferred.promise;
}
// 运行示例
var URL = "http://httpbin.org/get";
getURL(URL).then(function onFulfilled(value){
console.log(value);
}).catch(console.error.bind(console));
  • Deferred 不需要将代码用Promise括起来
  • 由于没有被嵌套在函数中,可以减少一层缩进
  • 反过来没有Promise里的错误处理逻辑

Promise代表了一个对象,这个对象的状态现在还不确定,但是未来一个时间点它的状态要么变为正常值(FulFilled),要么变为异常值(Rejected);
Deferred对象表示了一个处理还没有结束的这种事实,在它的处理结束的时候,可以通过Promise来取得处理结果。

11. 使用Promise.race来实现超时机制

1
2
3
4
5
6
7
8
//delayPromise.js
-----------------

function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//simple-timeout-promise.js
---------------------------

function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function timeoutPromise(promise, ms) {
var timeout = delayPromise(ms).then(function () {
throw new Error('Operation timed out after ' + ms + ' ms');
});
return Promise.race([promise, timeout]);
}

函数 timeoutPromise(比较对象promise, ms) 接收两个参数,第一个是需要使用超时机制的promise对象,第二个参数是超时时间,它返回一个由 Promise.race 创建的相互竞争的promise对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function timeoutPromise(promise, ms) {
var timeout = delayPromise(ms).then(function () {
throw new Error('Operation timed out after ' + ms + ' ms');
});
return Promise.race([promise, timeout]);
}
// 运行示例
var taskPromise = new Promise(function(resolve){
// 随便一些什么处理
var delay = Math.random() * 2000;
setTimeout(function(){
resolve(delay + "ms");
}, delay);
});
timeoutPromise(taskPromise, 1000).then(function(value){
console.log("taskPromise在规定时间内结束 : " + value);
}).catch(function(error){
console.log("发生超时", error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//delay-race-cancel-play.js
----------------------------

function copyOwnFrom(target, source) {
Object.getOwnPropertyNames(source).forEach(function (propName) {
Object.defineProperty(target, propName, Object.getOwnPropertyDescriptor(source, propName));
});
return target;
}
function TimeoutError() {
var superInstance = Error.apply(null, arguments);
copyOwnFrom(this, superInstance);
}
TimeoutError.prototype = Object.create(Error.prototype);
TimeoutError.prototype.constructor = TimeoutError;
function delayPromise(ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
}
function timeoutPromise(promise, ms) {
var timeout = delayPromise(ms).then(function () {
return Promise.reject(new TimeoutError('Operation timed out after ' + ms + ' ms'));
});
return Promise.race([promise, timeout]);
}
function cancelableXHR(URL) {
var req = new XMLHttpRequest();
var promise = new Promise(function (resolve, reject) {
req.open('GET', URL, true);
req.onload = function () {
if (req.status === 200) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function () {
reject(new Error(req.statusText));
};
req.onabort = function () {
reject(new Error('abort this request'));
};
req.send();
});
var abort = function () {
// 如果request还没有结束的话就执行abort
// https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
if (req.readyState !== XMLHttpRequest.UNSENT) {
req.abort();
}
};
return {
promise: promise,
abort: abort
};
}
var object = cancelableXHR('http://httpbin.org/get');
// main
timeoutPromise(object.promise, 1000).then(function (contents) {
console.log('Contents', contents);
}).catch(function (error) {
if (error instanceof TimeoutError) {
object.abort();
return console.log(error);
}
console.log('XHR Error :', error);
});
  1. 通过 cancelableXHR 方法取得包装了XHR的promise对象和取消该XHR请求的方法

  2. timeoutPromise 方法中通过 Promise.race 让XHR的包装promise和超时用promise进行竞争。

    • XHR在超时前返回结果的话

      • 和正常的promise一样,通过 then 返回请求结果
    • 发生超时的时候

      • 抛出 throw TimeoutError 异常并被 catch

      • catch的错误对象如果是 TimeoutError 类型的话,则调用 abort 方法取消XHR请求

12. 易错点

http://fex.baidu.com/blog/2015/07/we-have-a-problem-with-promises/

1. promises vs promises factories

当我们希望执行一个个的执行一个 promises 序列,即类似 Promise.all() 但是并非并行的执行所有 promises。

错误写法(传入 executeSequentially() 的 promises 依然会并行执行):

1
2
3
4
5
6
7
function executeSequentially(promises) {
var result = Promise.resolve();
promises.forEach(function (promise) {
result = result.then(promise);
});
return result;
}

其根源在于你所希望的,实际上根本不是去执行一个 promises 序列。
依照 promises 规范,一旦一个 promise 被创建,它就被执行了
因此你实际上需要的是一个 promise factories 数组。

正确写法:

1
2
3
4
5
6
7
8
9
10
11
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
result = result.then(promiseFactory);
});
return result;
}

function myPromiseFactory() {
return somethingThatCreatesAPromise();
}

一个 promises factory 仅仅是一个可以返回 promise 的函数, 在被执行之前并不会创建 promise。它就像一个 then 函数一样,而实际上,它们就是完全一样的东西。

2. promise依赖 & 命名函数

有时候,一个 promise 会依赖于另一个,但是如果我们希望同时获得这两个 promises 的输出。

错误写法:

1
2
3
4
5
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id);
}).then(function (userAccount) {
// dangit, I need the "user" object too!
});

正确的金字塔写法:

1
2
3
4
5
getUserByName('nolan').then(function (user) {
return getUserAccountById(user.id).then(function (userAccount) {
// okay, I have both the "user" and the "userAccount"
});
});

避免缩进问题,将函数抽离到一个命名函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function onGetUserAndUserAccount(user, userAccount) {
return doSomething(user, userAccount);
}

function onGetUser(user) {
return getUserAccountById(user.id).then(function (userAccount) {
return onGetUserAndUserAccount(user, userAccount);
});
}

getUserByName('nolan')
.then(onGetUser)
.then(function () {
// at this point, doSomething() is done, and we are back to indentation 0
});

3. promise穿透

1
2
3
4
5
6
Promise.resolve('foo').then(Promise.resolve('bar')).then(function (result) {
console.log(result);
});

---------------
result: foo

发生这个的原因是如果你像 then() 传递的并非是一个函数(比如 promise),它实际上会将其解释为 then(null),这就会导致前一个 promise 的结果会穿透下面。

1
2
3
Promise.resolve('foo').then(null).then(function (result) {
console.log(result);
});

then() 是期望获取一个函数。

正确写法:

1
2
3
4
5
6
7
8
Promise.resolve('foo').then(function () {
return Promise.resolve('bar');
}).then(function (result) {
console.log(result);
});

---------------
result: bar

13. 经典问题

Puzzle #1

1
2
3
doSomething().then(function () {
return doSomethingElse();
}).then(finalHandler);

Answer:

1
2
3
4
5
6
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|

Puzzle #2

1
2
3
doSomething().then(function () {
doSomethingElse();
}).then(finalHandler);

Answer:

1
2
3
4
5
6
doSomething
|-----------------|
doSomethingElse(undefined)
|------------------|
finalHandler(undefined)
|------------------|

Puzzle #3

1
2
doSomething().then(doSomethingElse())
.then(finalHandler);

Answer:

1
2
3
4
5
6
doSomething
|-----------------|
doSomethingElse(undefined)
|---------------------------------|
finalHandler(resultOfDoSomething)
|------------------|

Puzzle #4

1
2
doSomething().then(doSomethingElse)
.then(finalHandler);

Answer:

1
2
3
4
5
6
doSomething
|-----------------|
doSomethingElse(resultOfDoSomething)
|------------------|
finalHandler(resultOfDoSomethingElse)
|------------------|

5. 深入阅读

Promise 必知必会(十道题)

深入 Promise(一)——Promise 实现详解

深入 Promise(二)——进击的 Promise

深入 Promise(三)——命名 Promise